﻿using System;
using System.Windows;
using System.Windows.Controls;
using System.Text.RegularExpressions;

namespace Twilight.Controls
{     
    /// <summary>
    /// Based on the WrapPanel from the Silverlight Control Toolkit, but a simplified version
    /// since the tweets only wrap horizontally and not vertically
    /// http://www.codeplex.com/silverlight
    /// 
    /// Also based on logic from the LinkLabel project to turn text into a collection of TextBlocks
    /// and Hyperlinks so that the WrapPanel can display then as a single TextBlock.
    /// http://www.codeplex.com/SilverlightLinkLabel
    /// </summary>
    public abstract class TextWrapPanel : Panel
    {
        protected Regex linkRegex;
        private char[] delimiter = {' '};

        public Style TextStyle
        {
            get { return (Style)GetValue(TextStyleProperty); }
            set { SetValue(TextStyleProperty, value); }
        }

        // Using a DependencyProperty as the backing store for TextStyle.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty TextStyleProperty =
            DependencyProperty.Register("TextStyle", typeof(Style), typeof(TextWrapPanel), null);

        public Style LinkStyle
        {
            get { return (Style)GetValue(LinkStyleProperty); }
            set { SetValue(LinkStyleProperty, value); }
        }

        // Using a DependencyProperty as the backing store for LinkStyle.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty LinkStyleProperty =
            DependencyProperty.Register("LinkStyle", typeof(Style), typeof(TextWrapPanel), null);

        protected void OnTextChanged(string text)
        {
            this.Children.Clear();

            AddChildrenFromText(text);

            InvalidateMeasure();
        }

        protected abstract void ProcessLinkMatch(Match match);

        private void AddChildrenFromText(string text)
        {
            int position = 0;
            var links = linkRegex.Matches(text);

            for (int i = 0; i < links.Count; i++)
            {
                var match = links[i];
                var start = match.Index;

                if (start > position)
                {
                    AddWords(text.Substring(position, start - position));
                }

                ProcessLinkMatch(match);

                position = match.Index + match.Length;
            }

            // append the text after the last uri found
            if (position < text.Length)
            {
                AddWords(text.Substring(position));
            }
        }

        private void AddWords(string text)
        {
            var words = text.Split(delimiter, StringSplitOptions.RemoveEmptyEntries);
            foreach (string word in words)
            {
                Children.Add(new TextBlock()
                {
                    Text = word + " ",
                    Style = TextStyle
                });
            }
        }

        protected override Size MeasureOverride(Size availableSize)
        {
            var lineSize = new Size();
            var maxWidth = availableSize.Width;
            var totalSize = new Size();

            foreach (UIElement element in Children)
            {
                element.Measure(availableSize);
                if (lineSize.Width + element.DesiredSize.Width > maxWidth)
                {
                    totalSize.Width = Math.Max(lineSize.Width, totalSize.Width);
                    totalSize.Height += lineSize.Height;

                    lineSize = element.DesiredSize;

                    if (element.DesiredSize.Width > availableSize.Width)
                    {
                        totalSize.Width = Math.Max(element.DesiredSize.Width, totalSize.Width);
                        totalSize.Height += element.DesiredSize.Height;
                        lineSize = new Size();
                    }
                }
                else
                {
                    lineSize.Width += element.DesiredSize.Width;
                    lineSize.Height = Math.Max(lineSize.Height, element.DesiredSize.Height);
                }
            }

            totalSize.Width = Math.Max(lineSize.Width, totalSize.Width);
            totalSize.Height += lineSize.Height;

            return new Size(totalSize.Width, totalSize.Height);
        }

        protected override Size ArrangeOverride(Size finalSize)
        {
            var lineSize = new Size();

            double indirectOffset = 0;

            UIElementCollection children = Children;
            int count = children.Count;
            int lineStart = 0;
            for (int lineEnd = 0; lineEnd < count; lineEnd++)
            {
                UIElement element = children[lineEnd];

                if (lineSize.Width + element.DesiredSize.Width > finalSize.Width)
                {
                    ArrangeLine(lineStart, lineEnd, indirectOffset, lineSize.Width);

                    indirectOffset += lineSize.Height;
                    lineSize = element.DesiredSize;

                    if (element.DesiredSize.Width > finalSize.Width)
                    {
                        ArrangeLine(lineEnd, ++lineEnd, indirectOffset, element.DesiredSize.Width);

                        indirectOffset += lineSize.Height;
                        lineSize = new Size();
                    }

                    // Advance the start index to a new line after arranging
                    lineStart = lineEnd;
                }
                else
                {
                    // Otherwise just add the element to the end of the line
                    lineSize.Width += element.DesiredSize.Width;
                    lineSize.Height = Math.Max(lineSize.Height, element.DesiredSize.Height);
                }
            }

            // Arrange any elements on the last line
            if (lineStart < count)
            {
                ArrangeLine(lineStart, count, indirectOffset, lineSize.Height);
            }

            return finalSize;
        }

        private void ArrangeLine(int lineStart, int lineEnd, double indirectOffset, double indirectGrowth)
        {
            double directOffset = 0.0;

            UIElementCollection children = Children;
            for (int index = lineStart; index < lineEnd; index++)
            {
                UIElement element = children[index];

                // Arrange the element
                Rect bounds = new Rect(directOffset, indirectOffset, element.DesiredSize.Width, indirectGrowth);
                element.Arrange(bounds);

                directOffset += element.DesiredSize.Width;
            }
        }
    }
}
